#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

template <class T>
class BlockQueue
{
    const static size_t gMaxCap = 5;
public:
    BlockQueue(const size_t maxCap = gMaxCap): _maxCapacity(maxCap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_ccond, nullptr);
        pthread_cond_init(&_pcond, nullptr);
    }
    void push(const T& in)// 输入型参数 -> const &
    {
        // producer
        pthread_mutex_lock(&_mutex);

        // 1. 判断队列/“共享缓存”是否满了
        /*
        细节2：这里的判断不能只判断一次，即不能使用if，而是while。原因如下：
            假如，当前生产者有10个，消费者只有1个，当队列满时，10个生产者都调用pthread_cond_wait()而被挂起，
            消费者成功消费一次后，会调用pthread_cond_signal()唤醒生产者，如果这里使用的是pthread_cond_broadcast()，
            就会将10个生产者全部唤醒，因为是在if语句块中，所以，生产者线程可以直接跳出if语句去执行下面的代码，即生产数据，
            但是，理论上要生产的数据只有一个，但10个生产者线程都会生产，这就会出现bug。
            所以，生产者线程的判断应是在生产者线程被唤醒后还有再执行一次判断才行，所以应使用while来执行判断功能。
            消费者线程消费函数中的判断同理，也应用while。
        */
        while(is_full())
        {
            // 满了生产者就不能再生产
            pthread_cond_wait(&_pcond, &_mutex);
            /*
            细节1：
            int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
            该函数有两个参数，第二个参数是互斥锁。
                a. 该函数被调用时，会以原子性的方式释放传入的互斥锁，并将自己（线程）挂起。
                b. 该函数被唤醒返回时，会重新申请传入的互斥锁，然后返回。如果申请锁不成功，该线程就会处于竞争锁的状态，直到申请锁成功才返回。
            所以不必担心，线程在临界区被挂起但又占有锁不释放的问题。
            */
        }
        // 2. 走到这里，队列一定没有满，可以生产
        _q.push(in);// 生产
        // 3. 走到这里，队列一定有至少一个有效数据/“产品”，消费者可以消费
        pthread_cond_signal(&_ccond);// 唤醒消费者，使产品有被消费。这里可以指定策略，例如当至少有三分之一的产品时，才能够唤醒等待的消费者
        /*
        细节3：
        这里的唤醒功能，pthread_cond_signal()也可以放在临界区外生效。因为如果放在临界区外唤醒，就是先释放锁，然后再唤醒，在释放锁后，
        生产者线程依然要检测队列是否为空，若为空，依然会被挂起，和放在临界区内一样，都是直到唤醒消费者线程，待消费者线程消费成功后唤醒生产者线程，才可以向下执行。
        */
        pthread_mutex_unlock(&_mutex);
    }
    void pop(T* out)// 输出型参数 -> *   // 输入输出型参数 -> &
    {
        // consumer
        pthread_mutex_lock(&_mutex);
        // 1. 判断队列是否为空，即是否有产品
        while(is_empty())
        {
            // 队列为空，货架上没有产品，不能消费
            pthread_cond_wait(&_ccond, &_mutex);// 让消费者等待，直至有产品能够被消费
        }
        // 2. 走到这里，队列一定不为空，即货架上一定存在至少一个产品，可供消费者即刻消费
        *out = _q.front();// 消费者消费获得产品
        _q.pop();
        // 3. 走到这里，队列一定有至少一个空位，即货架上一定存在至少一个空产品位，允许生产者生产产品来销售
        pthread_cond_signal(&_pcond);// 唤醒一个等待的生产者。这里可以指定策略，例如当至多有三分之一待售卖产品时才开始生产

        pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_ccond);
        pthread_cond_destroy(&_pcond);
    }
private:
    bool is_empty() const
    {
        return _q.empty();
    }
    bool is_full() const
    {
        return _q.size() == _maxCapacity;
    }
private:
    std::queue<T> _q;
    size_t _maxCapacity;// 队列最大容量
    pthread_mutex_t _mutex;// 阻塞队列是共享资源，要有锁保护
    pthread_cond_t _ccond;// 消费者对应的条件变量 队列空了，消费者阻塞等待
    pthread_cond_t _pcond;// 生产者对应的条件变量 队列满了，生产者阻塞等待
};